﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NetfractionHub.LocalMachine.BotCommands;
using NetfractionHub.Network.Protocol;
using NetfractionHub.Network.Protocol.BASE.Factory;

namespace NetfractionHub.LocalMachine
{
	public class BotNode : RemoteMachine.Node
	{
		private Dictionary<string, CommandHandler> _botCommands;
		private List<string> _distinctCommandKeys;

		public Dictionary<string, CommandHandler> BotCommands
		{
			get { return _botCommands; }
			set { _botCommands = value; }
		}

		public List<string> DistinctCommandKeys
		{
			get { return _distinctCommandKeys; }
		}

		public BotNode()
		{
			this.Connection = new Network.ClientConnection(null, this);
			this.Connection.State = NetfractionHub.RemoteMachine.ConnectionState.Normal;

			this._botCommands = new Dictionary<string, CommandHandler>();
			this._distinctCommandKeys = new List<string>();
		}

		/// <summary>
		/// Use to send node information out. The bot is fully running on the hub after this method.
		/// </summary>
		public virtual void OnStart()
		{

		}

		public virtual void HandleCommandMessage(RemoteMachine.Node sourceNode, string message)
		{

		}

		public virtual void HandleChatMessage(RemoteMachine.Node sourceNode, ProtocolMessage protoMessage)
		{

		}

		private static Regex botParseRegex = null;
		protected virtual CommandMessage ParseMessage(RemoteMachine.Node targetNode, string message)
		{
			// not sure why we have to escape it again here...
			message = message.Replace(@"\\", @"\");

			CommandMessage newCommand = new CommandMessage();

			// command is in the format:
			// !name --toggleParameter -parameter:"value"
			// the regex ensures each parameter is prefixed by a space, and followed by a space or new line character (for the last parameter only, cannot have multi-line arguments)
			if (botParseRegex == null)
			{
				string prefixes = string.Empty;
				foreach (string s in BotNode.GetCommandPrefixes(this))
				{
					prefixes += Regex.Escape(s) + "|";
				}
				prefixes = prefixes.Substring(0, prefixes.Length - 1);
				botParseRegex = new Regex("(^(" + prefixes + ")(?<command>\\w+)(\\s|$))|((?<=\\s)--(?<toggleParameter>\\w+)(\\s|$))|((?<=\\s)-((?<parameter>\\w+):\"(?<value>.[^\"]*)\")(\\s|$))|((?<=\\s)(\"(?<unnamedParameter>.[^\"]*)\")(\\s|$))|((?<=\\s)((?<undecoratedParameter>.[^\\s]*))(\\s|$))", RegexOptions.ECMAScript);
			}

			if (botParseRegex.Matches(message).Count == 0)
			{
				return null;
			}

			foreach (Match expMatch in botParseRegex.Matches(message))
			{
				if (expMatch.Groups["command"].Length > 0)
				{
					newCommand.Name = expMatch.Groups["command"].Value.ToLower(System.Globalization.CultureInfo.InvariantCulture);
				}
				if (expMatch.Groups["toggleParameter"].Length > 0)
				{
					if (!newCommand.BooleanArguments.ContainsKey(expMatch.Groups["toggleParameter"].Value))
					{
						// need to XOR (^) with the current value of the argument to toggle it
						newCommand.BooleanArguments.Add(expMatch.Groups["toggleParameter"].Value, true);
					}
					else
					{
						Network.ConnectionManager.SendStatusMessageToNode(targetNode,
							FactorySTA.StatusSeverity.RecoverableError,
							FactorySTA.StatusErrorCode.Generic,
							String.Format(System.Globalization.CultureInfo.InvariantCulture, "Can only include parameter once: {0}", expMatch.Groups["toggleParameter"]));
						return null;
					}
				}
				if (expMatch.Groups["parameter"].Length > 0 && expMatch.Groups["value"].Length > 0)
				{
					if (!newCommand.StringArguments.ContainsKey(expMatch.Groups["parameter"].Value))
					{
						newCommand.StringArguments.Add(expMatch.Groups["parameter"].Value, expMatch.Groups["value"].Value);
					}
					else
					{
						Network.ConnectionManager.SendStatusMessageToNode(targetNode,
							FactorySTA.StatusSeverity.RecoverableError,
							FactorySTA.StatusErrorCode.Generic,
							String.Format(System.Globalization.CultureInfo.InvariantCulture, "Can only include parameter once: {0}", expMatch.Groups["parameter"]));
						return null;
					}
				}
				if (expMatch.Groups["unnamedParameter"].Length > 0)
				{
					newCommand.UnnamedArguments.Add(expMatch.Groups["unnamedParameter"].Value);
				}
				if (expMatch.Groups["undecoratedParameter"].Length > 0)
				{
					newCommand.UnnamedArguments.Add(expMatch.Groups["undecoratedParameter"].Value);
				}
			}
			return newCommand;
		}

		public void SendEchoMessageFromBot(RemoteMachine.Node targetNode, string message)
		{
			ProtocolMessageFactory factory = new FactoryMSG();
			Dictionary<int, object> factoryParams = new Dictionary<int, object>();
			factoryParams.Add((int)FactoryMSG.ParameterEnum.SessionID, this.SessionId);
			factoryParams.Add((int)FactoryMSG.ParameterEnum.TargetID, targetNode.SessionId);
			factoryParams.Add((int)FactoryMSG.ParameterEnum.Text, message);

			Network.ConnectionManager.SendChatMessage(factory.Create(ProtocolMessage.MessageTypeEnum.Echo, factoryParams));
		}

		public void SendPrivateMessageFromBot(RemoteMachine.Node targetNode, string message)
		{
			ProtocolMessageFactory factory = new FactoryMSG();
			Dictionary<int, object> factoryParams = new Dictionary<int, object>();
			factoryParams.Add((int)FactoryMSG.ParameterEnum.SessionID, this.SessionId);
			factoryParams.Add((int)FactoryMSG.ParameterEnum.TargetID, targetNode.SessionId);
			factoryParams.Add((int)FactoryMSG.ParameterEnum.Text, message);
			factoryParams.Add((int)FactoryMSG.ParameterEnum.PrivateMessage, Settings.HubSettings.SecurityBot.SessionId);

			Network.ConnectionManager.SendChatMessage(factory.Create(ProtocolMessage.MessageTypeEnum.Echo, factoryParams));
		}

		public void SendBroadcastMessageFromBot(string message)
		{
			if (!string.IsNullOrEmpty(message))
			{
				ProtocolMessageFactory factory = new FactoryMSG();
				Dictionary<int, object> factoryParams = new Dictionary<int, object>();
				factoryParams.Add((int)FactoryMSG.ParameterEnum.SessionID, this.SessionId);
				factoryParams.Add((int)FactoryMSG.ParameterEnum.Text, message);

				Network.ConnectionManager.SendChatMessage(factory.Create(ProtocolMessage.MessageTypeEnum.Broadcast, factoryParams));
			}
		}

		protected void AddCommand(CommandHandler handler)
		{
			object[] attrs = handler.GetType().GetCustomAttributes(typeof(CommandHandlerAttribute), false);
			if (attrs.Length > 0)
			{
				handler.ParentBot = this;

				CommandHandlerAttribute attrib = attrs[0] as CommandHandlerAttribute;
				_botCommands.Add(attrib.Trigger, handler);
				_distinctCommandKeys.Add(attrib.Trigger);

				if (!string.IsNullOrEmpty(attrib.TriggerAlias))
				{
					_botCommands.Add(attrib.TriggerAlias, handler);
				}
			}
		}

		private static List<string> commandPrefixesCache;
		public static List<string> GetCommandPrefixes(BotNode bot)
		{
			if (commandPrefixesCache != null)
				return commandPrefixesCache;

			List<string> prefixes = new List<string>();

			foreach (BotNodeAttribute attrib in bot.GetType().GetCustomAttributes(typeof(BotNodeAttribute), false)
					.Where(p => p is BotNodeAttribute))
			{
				if (!string.IsNullOrEmpty(attrib.CommandPrefix))
				{
					prefixes.Add(attrib.CommandPrefix);
				}
			}

			commandPrefixesCache = prefixes;
			return prefixes;
		}
	}

	[global::System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
	public sealed class BotNodeAttribute : Attribute
	{
		private string _commandPrefix;

		public BotNodeAttribute()
		{
			_commandPrefix = string.Empty;
		}

		public string CommandPrefix
		{
			get { return _commandPrefix; }
			set { _commandPrefix = value; }
		}
	}
}
